package microcredit.gui.editor;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;

import javax.swing.ImageIcon;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTabbedPane;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;

import com.lowagie.text.Font;

import microcredit.gui.base.BaseAction;
import microcredit.gui.base.BaseDialog;
import microcredit.gui.selector.AbstractButtonPanel;
import microcredit.gui.util.DialogUtilities;
import microcredit.gui.util.ExceptionDialog;
import microcredit.gui.util.MessageUtilities;
import microcredit.gui.util.UIStateCapture;
import net.miginfocom.swing.MigLayout;

public class EditorPanel<E> extends AbstractButtonPanel {

	private enum State {
		SAVED, REFRESHED, MODIFIED, CLEAN
	}
	
	private IRefreshable<E> refreshable;
	private ISaveable<E> saveable;
	private ICloseable<E> closeable;
	private IPrintable<E> printable;
	private E element;
	
	private RefreshAction refreshAction;
	private CloseAction closeAction;
	private SaveAction saveAction;
	private PrintAction printAction;
	
	private List<AbstractEditorForm<E>> forms;
	private JTabbedPane tabbedPane;
	
	private boolean dirty;
	
	private boolean hideSaveMessage = true;
	private boolean closeOnSave = false;
	private boolean alwaysDirty = false;
	
	private JLabel statusLabel;
	
	@SuppressWarnings("unchecked")
	private ComponentAdapter componentAdapter = new ComponentAdapter() {
		public void componentShown(java.awt.event.ComponentEvent e) {
			((AbstractEditorForm<E>)e.getComponent()).restoreFocusOwner();
		};
		public void componentHidden(java.awt.event.ComponentEvent e) {
			((AbstractEditorForm<E>)e.getComponent()).storeFocusOwner();
		};
	};
	
	private PropertyChangeListener titleChangedListener = new PropertyChangeListener() {
		@SuppressWarnings("unchecked")
		@Override
		public void propertyChange(PropertyChangeEvent evt) {
			AbstractEditorForm<E> form = (AbstractEditorForm<E>)evt.getSource();
			tabbedPane.setTitleAt(forms.indexOf(form), (String)evt.getNewValue());
		}
	};
	
	private PropertyChangeListener iconChangedListener = new PropertyChangeListener() {
		@SuppressWarnings("unchecked")
		@Override
		public void propertyChange(PropertyChangeEvent evt) {
			AbstractEditorForm<E> form = (AbstractEditorForm<E>)evt.getSource();
			tabbedPane.setIconAt(forms.indexOf(form), (ImageIcon)evt.getNewValue());
		}
	};
	
	public EditorPanel(Class<E> clazz, 
			E element,
			IRefreshable<E> refreshable, 
			ISaveable<E> saveable,
			ICloseable<E> closeable,
			IPrintable<E> printable, List<AbstractEditorForm<E>> forms) {
		this.refreshable = refreshable;
		this.closeable = closeable;
		this.printable = printable;
		this.saveable = saveable;
		this.forms = forms;
		this.element = element;
		getMainPanel().setLayout(new MigLayout("", "0[grow]0", "0[grow]"));
		createActions();
		applyForms();
		emulateActionWithoutThrow(refreshAction);
		updateState();
		
		setPreferredSize(calculateDefaultSize());
		setState(State.CLEAN);
	}
	
	private Dimension calculateDefaultSize() {
		int maxWidth = 0;
		int maxHeight = 0;
		for (AbstractEditorForm<E> form : forms) {
			Dimension defaultSize = form.getDefaultSize();
			if (defaultSize != null) {
				if (defaultSize.width > maxWidth) {
					maxWidth = defaultSize.width;
				}
				if (defaultSize.height > maxHeight) {
					maxHeight = defaultSize.height;
				}
			}
		}
		if (maxWidth <= 0) {
			maxWidth = 650;
		}
		if (maxHeight <= 0) {
			maxHeight = 500;
		}
		return new Dimension(maxWidth, maxHeight);
	}

	private void setState(State state) {
		switch (state) {
		case CLEAN :
			applyToTitle(" "+element.toString());
			statusLabel.setText("");
			break;
		case MODIFIED :
			applyToTitle(" "+element.toString()+" [Modified]");
			statusLabel.setText("");
			break;
		case REFRESHED :
			applyToTitle(" "+element.toString());
			break;
		case SAVED :
			applyToTitle(" "+element.toString());
			statusLabel.setText("Saved!");
			statusLabel.setForeground(Color.decode("#35ae21"));
			break;
		}
	}
	
	String originalTitle = null;
	
	private void applyToTitle(String suffix) {
		Window w = SwingUtilities.getWindowAncestor(this);
		if (originalTitle == null) {
			if (w instanceof JDialog) {
				originalTitle = ((JDialog) w).getTitle();
			} else if (w instanceof JFrame) {
				originalTitle = ((JFrame) w).getTitle();
			}
		}
		
		String title = originalTitle + suffix;
		if (w instanceof JDialog) {
			((JDialog) w).setTitle(title);
		} else if (w instanceof JFrame) {
			((JDialog) w).setTitle(title);
		}
	}
	
	public boolean isDirty() {
		return alwaysDirty || dirty;
	}
	
	public void setAlwaysDirty(boolean alwaysDirty) {
		this.alwaysDirty = alwaysDirty;
		updateState();
	}
	
	public void setDirty(boolean dirty) {
		this.dirty = dirty;
		updateState();
		if (dirty && !alwaysDirty) {
			setState(State.MODIFIED);
		}
	}
	
	public void setCloseOnSave(boolean closeOnSave) {
		this.closeOnSave = closeOnSave;
	}
	
	public void setHideSaveMessage(boolean hideSaveMessage) {
		this.hideSaveMessage = hideSaveMessage;
	}
	
	public BaseAction getCloseAction() {
		return closeAction;
	}

	private void handleClosable() {
		try {
			if (!alwaysDirty && isDirty()) {
				int option = JOptionPane.showConfirmDialog(this, "There are unsaved changed.\n\nDo you want to save your changes?");
				if (option == JOptionPane.YES_OPTION) {
					emulateAction(saveAction);
				} else if (option == JOptionPane.NO_OPTION) {
					// just continue
				} else {
					return;
				}
			}
			closeable.close(element);
		} catch (Throwable e1) {
			ExceptionDialog.handleException(this, e1);
		}
	}
	
	private void applyForms() {
		getMainPanel().removeAll();
		if (forms.size() > 1) {
			tabbedPane = new JTabbedPane();
			for (final AbstractEditorForm<E> form : forms) {
				tabbedPane.addTab(form.getTitle(), form);
				form.addPropertyChangeListener("title", titleChangedListener);
				form.addPropertyChangeListener("icon", iconChangedListener);
				form.addComponentListener(componentAdapter);
			}
			getMainPanel().add(tabbedPane, "grow");
		} else if (forms.size() == 1){
			getMainPanel().add(forms.get(0), "grow");
		}
	}
	
	private void createActions() {
		if (closeable != null) {
			closeAction = new CloseAction(this);
		}
		if (saveable != null) {
			saveAction = new SaveAction(this);
		}
		if (refreshable != null) {
			refreshAction = new RefreshAction(this);
		}
		if (printable != null) {
			printAction = new PrintAction(this);
		}
		
		addAction(refreshAction);
		addAction(printAction);
		addExpander();
		
		statusLabel = addLabel("");
		statusLabel.setForeground(Color.BLUE);
		statusLabel.setFont(statusLabel.getFont().deriveFont(10f));
		statusLabel.setFont(statusLabel.getFont().deriveFont(Font.BOLD));
		setState(State.CLEAN);
		
		addAction(saveAction);
		addAction(closeAction);
	}
	
	private class RefreshAction extends BaseAction {
		public RefreshAction(Component parent) {
			super(parent);
			setName("Refresh");
			setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0));
			setIcon("x16/refresh.png");
		}

		@Override
		public void perform(ActionEvent e) throws Throwable {
			if (isDirty() && !isEmulate()) {
				int option = JOptionPane.showConfirmDialog(EditorPanel.this, "There are unsaved changes.\nRefresh will discard your changes.\n\nDo you want to continue?");
				if (option != JOptionPane.YES_OPTION) {
					return;
				}
			}
			
			element = refreshable.refresh(element);
			
			for (AbstractEditorForm<E> form : forms) {
				form.apply(element);
			}
			
			setDirty(false);

			if (!isEmulate()) {
				setState(State.REFRESHED);
			}
			
			EditorPanel.this.updateState();
			final UIStateCapture capture = UIStateCapture.capture(EditorPanel.this.getMainPanel());
			capture.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					setDirty(true);
					UIStateCapture.destroy(capture);
				}
			});
		}
	}
	
	private class SaveAction extends BaseAction {
		
		public SaveAction(Component parent) {
			super(parent);
			setName("Save");
			setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_DOWN_MASK));
			setIcon("x16/disk_blue.png");
		}
		@Override
		public void perform(ActionEvent e) throws Throwable {
			for (AbstractEditorForm<E> form : forms) {
				element = form.receive(element);
			}
			for (AbstractEditorForm<E> form : forms) {
				if (!form.onSave(element)) {
					return;
				}
			}
			element = saveable.save(element);
			
			emulateAction(refreshAction);
			
			if (!hideSaveMessage) {
				MessageUtilities.showInformation(EditorPanel.this, "Save successful", "Entry successfully saved!");
			}
			
			setState(State.SAVED);
			
			for (AbstractEditorForm<E> form : forms) {
				form.afterSave(element);
			}
			
			if (closeOnSave) {
				handleClosable();
			}
		}
		
		@Override
		public void updateState() throws Throwable {
			setEnabled(isDirty());
		}
	}
	
	private class PrintAction extends BaseAction {
		
		public PrintAction(Component parent) {
			super(parent);
			setName("Print");
			setIcon("x16/printer.png");
			setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, KeyEvent.CTRL_DOWN_MASK));
		}
		
		@Override
		public void perform(ActionEvent e) throws Throwable {
			printable.print(getElement());
		}
	}
	
	private class CloseAction extends BaseAction {
		public CloseAction(Component parent) {
			super(parent);
			setName("Close");
			setIcon("x16/delete2.png");
			setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0));
		}
		@Override
		public void perform(ActionEvent e) throws Throwable {
			handleClosable();
//			element = null; // -> no return value / cancelled
		}
	}
	
	public E getElement() {
		return element;
	}
	
	public static <E> E showEditorDialog(Component parent, 
			String title,
			Class<E> clazz, 
			E element,
			IRefreshable<E> refreshable, 
			ISaveable<E> saveable,
			IPrintable<E> printable, List<AbstractEditorForm<E>> forms) {
		EditorDialog<E> dialog = createEditorDialog(parent, title, clazz, element, refreshable, saveable, printable, forms);
		dialog.setVisible(true);
		return dialog.getElement();
	}
	
	public static <E> EditorDialog<E> createEditorDialog(Component parent, 
			String title,
			Class<E> clazz, 
			E element,
			IRefreshable<E> refreshable, 
			ISaveable<E> saveable,
			IPrintable<E> printable, List<AbstractEditorForm<E>> forms) {
		@SuppressWarnings("unchecked")
		final EditorDialog<E> dialog = (EditorDialog<E>)DialogUtilities.createModal(parent, title, null, EditorDialog.class);
		final ICloseable<E> closable = new ICloseable<E>() {
			public void close(E e) throws Throwable {
				dialog.dispose();
			};
		};
		dialog.setTitle(title);
		final EditorPanel<E> panel = new EditorPanel<E>(clazz, element, refreshable, saveable, closable, printable, forms);
		dialog.setPanel(panel);
		dialog.getContentPane().add(panel);
		panel.setState(State.CLEAN);
		
		dialog.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				panel.handleClosable();
			}
		});
		
		dialog.pack();
		dialog.setLocationRelativeTo(DialogUtilities.getWindowForComponent(parent));
		return dialog;
	}
	
	
	public static class EditorDialog<E> extends BaseDialog {
		
		private static final long serialVersionUID = 1L;
		private EditorPanel<E> panel;
		
		public EditorDialog(Window owner, ModalityType modalityType) {
			super(owner, modalityType);
			addWindowListener(new WindowAdapter() {
				@Override
				public void windowOpened(WindowEvent e) {
						panel.forms.get(0).focusDefault();
				}
			});
		}

		private void setPanel(EditorPanel<E> panel) {
			this.panel = panel;
		}
		
		public EditorPanel<E> getPanel() {
			return panel;
		}
		public E getElement() {
			return panel.getElement();
		}
		
	}
	
		
}
